﻿using System.Collections.Generic;
using UnityEngine;

namespace CWM.Skinn
{
    public static partial class SkinnEx
    {
        private static class CopyBlendshapesLODUtility
        {
            public const RenderTextureReadWrite RTReadWrite = RenderTextureReadWrite.Linear;
            public const RenderTextureFormat RTFormat = RenderTextureFormat.Default;

            private static Material color2D = null;
            public static Material Color2DMaterial
            {
                get
                {
                    if (color2D == null)
                    {
                        var shaderName = "Hidden/Skinn/2D/VertexColor/Unlit";
                        var shader = Shader.Find(shaderName);
                        if (shader)
                        {
                            color2D = new Material(shader) { name = "VertexColor2D", hideFlags = HideFlags.HideAndDontSave };
                            SkinnGizmos.MaterialsToCleanUp.Add(color2D);
                        }
                        else
                        {
                            Debug.LogError(string.Format("Missing Shader \"{0}\"", shaderName));
                        }
                    }
                    return color2D;
                }
            }

            public static void ConfigureSampleTexture(Texture texture, Texture2D sampleTexture)
            {
                RenderTexture active = RenderTexture.active;
                RenderTexture temp = RenderTexture.GetTemporary(texture.width, texture.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);
                RenderTexture.active = temp;
                Graphics.Blit(texture, temp);
                sampleTexture.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0);
                sampleTexture.Apply();
                RenderTexture.active = active;
                RenderTexture.ReleaseTemporary(temp);
            }

            public static RenderTexture Write(Mesh sharedMesh, int index, RenderTexture tempRTexture)
            {
                var rt = RenderTexture.GetTemporary(tempRTexture.width, tempRTexture.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);

                RenderTexture.active = rt;
                GL.Clear(true, true, new Color(0.5f, 0.5f, 0.5f, 0f));
                Color2DMaterial.SetPass(0);
                Graphics.DrawMeshNow(sharedMesh, Matrix4x4.identity, index);

                RenderTexture.active = tempRTexture;
                GL.Clear(true, true, new Color(0.5f, 0.5f, 0.5f, 0f));

                var shader = SkinnInternalAsset.Asset.texturePadding;
                var kernal = shader.FindKernel("AlphaPadding");
                shader.SetTexture(kernal, "Source", rt);
                shader.SetTexture(kernal, "Destination", tempRTexture);
                shader.SetFloat("Size", 1.01f);
                shader.Dispatch(kernal, (tempRTexture.width + 7) / 8, (tempRTexture.height + 7) / 8, 1);
                Graphics.Blit(tempRTexture, rt);

                RenderTexture.active = rt;
                Color2DMaterial.SetPass(0);
                Graphics.DrawMeshNow(sharedMesh, Matrix4x4.identity, index);
                return rt;
            }
          
        }

        /// <summary>
        /// Requirements:
        /// Matching sub-mesh count and order.
        /// No Overlapping UV layouts per sub-mesh.
        /// </summary>
        /// <param name="targetMesh"></param>
        /// <param name="sourceMesh"></param>
        /// <param name="quality"></param>
        /// <returns></returns>
        public static bool CopyBlendshapesLOD(this Mesh targetMesh, Mesh sourceMesh,
            RenderQuality quality = RenderQuality.x1024, UVLayout targetChannel = UVLayout.Defualt, UVLayout sourceChannel = UVLayout.Defualt)
        {
            if (!SkinnInternalAsset.CanCompute() || IsNullOrNotVailid(sourceMesh) || IsNullOrNotVailid(targetMesh) || sourceMesh.subMeshCount != targetMesh.subMeshCount || !CopyBlendshapesLODUtility.Color2DMaterial) return false;

            var rtSize = (int)quality;
            var useSubMeshes = false;//TODO//
            var subMeshCount = useSubMeshes ? sourceMesh.subMeshCount : 1;
            var vertexSampleTexture = new Texture2D(rtSize, rtSize) { name = "VSampleTexture", hideFlags = HideFlags.HideAndDontSave };
            var normalSampleTexture = new Texture2D(rtSize, rtSize) { name = "NSampleTexture", hideFlags = HideFlags.HideAndDontSave };
            var tangentSampleTexture = new Texture2D(rtSize, rtSize) { name = "TSampleTexture", hideFlags = HideFlags.HideAndDontSave };

            var lastActiveRenderTexture = RenderTexture.active;
            var lastVertexColors = sourceMesh.colors;
            var lastUV = sourceMesh.uv;
            var targetUV = targetMesh.uv;

            switch (sourceChannel)
            {
                case UVLayout.Defualt: default: break;
                case UVLayout.One: sourceMesh.uv = sourceMesh.uv2; break;
                case UVLayout.Two: sourceMesh.uv = sourceMesh.uv3; break;
                case UVLayout.Three: sourceMesh.uv = sourceMesh.uv4; break;
            }

            switch (targetChannel)
            {
                case UVLayout.Defualt: default: break;
                case UVLayout.One: targetUV = targetMesh.uv2; break;
                case UVLayout.Two: targetUV = targetMesh.uv3; break;
                case UVLayout.Three: targetUV = targetMesh.uv4; break;
            }

            var duplicates = targetMesh.vertices.GetUniqueIndices();
            var subMeshIndex = useSubMeshes ? targetMesh.GetSubMeshIndices() : new int[targetMesh.vertices.Length];
            var existingShapeNames = new List<string>(targetMesh.GetBlendshapeNames());

            var vertexTextures = new RenderTexture[subMeshCount];
            var normalTextures = new RenderTexture[subMeshCount];
            var tangentTextures = new RenderTexture[subMeshCount];

            //var tempRTexture = RenderTexture.GetTemporary(rtSize, rtSize, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);
            var tempRTexture = new RenderTexture(rtSize, rtSize, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear)
            {
                enableRandomWrite = true
            };
            tempRTexture.Create();

            for (int shapeIndex = 0; shapeIndex < sourceMesh.blendShapeCount; shapeIndex++)
            {
                var shapeName = sourceMesh.GetBlendShapeName(shapeIndex);
                if (existingShapeNames.Contains(shapeName)) continue;
                var frameCount = sourceMesh.GetBlendShapeFrameCount(shapeIndex);
                for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
                {
                    var deltaVertices = new Vector3[sourceMesh.vertices.Length];
                    var deltaNormals = new Vector3[sourceMesh.vertices.Length];
                    var deltaTangents = new Vector3[sourceMesh.vertices.Length];

                    sourceMesh.GetBlendShapeFrameVertices(shapeIndex, frameIndex, deltaVertices, deltaNormals, deltaTangents);

                    var vertexColors = new Color[deltaVertices.Length];
                    var normalColors = new Color[deltaVertices.Length];
                    var tangentColors = new Color[deltaVertices.Length];

                    var deltaMag = 0.0f;
                    for (int i = 0; i < vertexColors.Length; i++) deltaMag = Mathf.Max(deltaMag, deltaVertices[i].magnitude * 2.0f);

                    var hasNormals = false;
                    var hasTangents = false;

                    for (int i = 0; i < vertexColors.Length; i++)
                    {
                        vertexColors[i].r = 0.5f;
                        vertexColors[i].g = 0.5f;
                        vertexColors[i].b = 0.5f;

                        normalColors[i].r = 0.5f;
                        normalColors[i].g = 0.5f;
                        normalColors[i].b = 0.5f;

                        tangentColors[i].r = 0.5f;
                        tangentColors[i].g = 0.5f;
                        tangentColors[i].b = 0.5f;

                        vertexColors[i].a = 1f;
                        normalColors[i].a = 1f;
                        tangentColors[i].a = 1f;

                        if (deltaVertices[i].magnitude > 1f.ToUnitFromStandard(ConversionUnits.Inch) * 0.1f)
                        {
                            vertexColors[i].r = (deltaVertices[i].x / deltaMag) + 0.5f;
                            vertexColors[i].g = (deltaVertices[i].y / deltaMag) + 0.5f;
                            vertexColors[i].b = (deltaVertices[i].z / deltaMag) + 0.5f;

                            normalColors[i].r = (deltaNormals[i].x / 2f) + 0.5f;
                            normalColors[i].g = (deltaNormals[i].y / 2f) + 0.5f;
                            normalColors[i].b = (deltaNormals[i].z / 2f) + 0.5f;

                            tangentColors[i].r = (deltaTangents[i].x / 2f) + 0.5f;
                            tangentColors[i].g = (deltaTangents[i].y / 2f) + 0.5f;
                            tangentColors[i].b = (deltaTangents[i].z / 2f) + 0.5f;
                        }

                        //if (!hasNormals && deltaNormals[i].sqrMagnitude > 0f) { hasNormals = true; }
                        //if (!hasTangents && deltaTangents[i].sqrMagnitude > 0f) { hasTangents = true; }
                    }

                    for (int i = 0; i < subMeshCount; i++)
                    {
                        sourceMesh.colors = vertexColors;
                        vertexTextures[i] = CopyBlendshapesLODUtility.Write(sourceMesh, i, tempRTexture);

                        if (hasNormals)
                        {
                            sourceMesh.colors = normalColors;
                            normalTextures[i] = CopyBlendshapesLODUtility.Write(sourceMesh, i, tempRTexture);
                        }
                        if (hasTangents)
                        {
                            sourceMesh.colors = tangentColors;
                            tangentTextures[i] = CopyBlendshapesLODUtility.Write(sourceMesh, i, tempRTexture);
                        }
                    }

                    var deltas = new Vector3[targetMesh.vertices.Length];
                    Vector3[] normals = hasNormals ? new Vector3[targetMesh.vertices.Length] : null;
                    Vector3[] tangents = hasTangents ? new Vector3[targetMesh.vertices.Length] : null;

                    for (int i = 0; i < subMeshCount; i++)
                    {
                        RenderTexture rtVertex, rtNormal = null, rtTangent = null;

                        rtVertex = vertexTextures[i];
                        CopyBlendshapesLODUtility.ConfigureSampleTexture(rtVertex, vertexSampleTexture);

                        if (hasNormals)
                        {
                            rtNormal = normalTextures[i];
                            CopyBlendshapesLODUtility.ConfigureSampleTexture(rtNormal, normalSampleTexture);
                        }
                        if (hasTangents)
                        {
                            rtTangent = tangentTextures[i];
                            CopyBlendshapesLODUtility.ConfigureSampleTexture(rtTangent, tangentSampleTexture);
                        }

                        for (int ii = 0; ii < deltas.Length; ii++)
                        {
                            if (subMeshIndex[ii] != i) continue;

                            if (duplicates[ii] == ii)
                            {
                                var vcolor = vertexSampleTexture.GetPixel((int)(rtVertex.width * targetUV[ii].x), (int)(rtVertex.height * targetUV[ii].y));
                                deltas[ii].x = (vcolor.r - 0.5f) * deltaMag;
                                deltas[ii].y = (vcolor.g - 0.5f) * deltaMag;
                                deltas[ii].z = (vcolor.b - 0.5f) * deltaMag;

                                if (hasNormals)
                                {
                                    var ncolor = normalSampleTexture.GetPixel((int)(rtNormal.width * targetUV[ii].x), (int)(rtNormal.height * targetUV[ii].y));
                                    normals[ii].x = (ncolor.r - 0.5f) * 2f;
                                    normals[ii].y = (ncolor.g - 0.5f) * 2f;
                                    normals[ii].z = (ncolor.b - 0.5f) * 2f;
                                }

                                if (hasTangents)
                                {
                                    var tcolor = tangentSampleTexture.GetPixel((int)(rtTangent.width * targetUV[ii].x), (int)(rtTangent.height * targetUV[ii].y));
                                    tangents[ii].x = (tcolor.r - 0.5f) * 2f;
                                    tangents[ii].y = (tcolor.g - 0.5f) * 2f;
                                    tangents[ii].z = (tcolor.b - 0.5f) * 2f;
                                }
                            }
                            else
                            {
                                deltas[ii] = deltas[duplicates[ii]];
                                if (hasNormals) normals[ii] = normals[duplicates[ii]];
                                if (hasTangents) tangents[ii] = tangents[duplicates[ii]];
                            }
                        }
                        RenderTexture.ReleaseTemporary(rtVertex);
                        if (rtNormal) RenderTexture.ReleaseTemporary(rtNormal);
                        if (rtTangent) RenderTexture.ReleaseTemporary(rtTangent);
                    }

                    var frameWeight = sourceMesh.GetBlendShapeFrameWeight(shapeIndex, frameIndex);
                    targetMesh.AddBlendShapeFrame(shapeName, frameWeight, deltas, normals, tangents);
                }
            }

            Release(tempRTexture);
            //RenderTexture.ReleaseTemporary(tempRTexture);
            RenderTexture.active = lastActiveRenderTexture;
            sourceMesh.colors = lastVertexColors;
            sourceMesh.uv = lastUV;
            Release(vertexSampleTexture);
            Release(normalSampleTexture);
            Release(tangentSampleTexture);
            return true;
        }
    }
}
